//
//  SEChatViewModel.swift
//  SmartEyeForPatient
//
//  Created by QDSG on 2021/4/27.
//

import UIKit
import YYText

class SEChatViewModel: SEBaseViewModel {
    var chatRecordList: [SEChatRecord] = []
    var inquiryInfo = SEInquiryInfo()
    var chatRecord = SEChatRecord()
    
    var reloadTableView: (([SEChatRecord], SEHttpStatus) -> Void)?
    var confirmOnlineInquiryHandler: ((SEHttpStatus) -> Void)?
    var updateFileAuthorizationHandler: ((IndexPath, SEHttpStatus) -> Void)?
    
    // MARK: - 聊天消息
    
    private(set) var isCountDown = false
    
    var isFirstLoad: Bool = false
    
    var numberOfRows: Int { chatRecordList.count }
    
    var senderType: SEChatSenderType { SEChatSenderType(rawValue: chatRecord.relativeType) ?? .patient }
    var messageType: SEChatMessageType { SEChatMessageType(rawValue: chatRecord.msgType) ?? .text }
    var chatType: SEChatType { SEChatType(rawValue: chatRecord.chatType) ?? .unknown }
    var msgFromType: SEChatMessageFromType { SEChatMessageFromType(rawValue: chatRecord.ackType) ?? .private }
    
    var messageId: String?
    var generateMessageIdSuccess: ((SEChatMessageType, SEHttpStatus) -> Void)?
    
    var fromMe: Bool { chatRecord.fromUserId == UserDefaults.userId }
    var richTextLayout: YYTextLayout? {
        // 初始化 YYTextContainer
        let textContainer = YYTextContainer()
        textContainer.size = CGSize(width: kChatTextMaxWidth, height: .greatestFiniteMagnitude)
        textContainer.linePositionModifier = richTextLinePositionModifier
        textContainer.maximumNumberOfRows = 0
        
        // 设置 layout
        let textLayout = YYTextLayout(container: textContainer,
                                      text: richTextAttributedString ?? NSMutableAttributedString(string: ""))
        return textLayout
    }
    var richTextLinePositionModifier: SEYYTextLinePositionModifier {
        // 初始化排版布局对象
        let modifier = SEYYTextLinePositionModifier(font: UIFont.regular(14))
        return modifier
    }
    var richTextAttributedString: NSMutableAttributedString? {
        // 解析富文本
        let image = messageType == .videoCall ?
            Asset.iconChatVideoRight.image : nil
        let content = messageType == .videoCall ?
            "视频通话" : (chatRecord.content ?? "")
        let attributedString = SEChatTextParser.parseText(
            content,
            font: UIFont.regular(14),
            image: image,
            fromMe: fromMe)
        return attributedString
    }
    var avatar: String? {
        fromMe ? UserDefaults.userAvatar : inquiryInfo.otherPhoto
    }
    var avatarPlaceholder: UIImage? {
        fromMe
            ? (inquiryInfo.patientSex == 1 ? Asset.iconPatientAvatarMale.image
                : Asset.iconPatientAvatarFemale.image)
            : (inquiryInfo.doctorSex == 1 ? Asset.iconDoctorAvatarMale.image : Asset.iconDoctorAvatarFemale.image)
    }
    
    var chatActionBarHidden: Bool {
        if inquiryInfo.type == .guide {
            return inquiryInfo.status == .finish
        } else {
            switch inquiryInfo.dealStatus {
            case .confirming, .confirmed, .waitEnd, .rejectEnd:
                return false
            default:
                return true
            }
        }
    }
    
    var bubbleImage: UIImage {
        let stretchInsets = UIEdgeInsets(top: 30, left: 28, bottom: 23, right: 28)
        // 拉伸图片区域
        let stretchImage = fromMe ? Asset.iconBubbleSender.image : Asset.iconBubbleReceiver.image
        let bubbleImage = stretchImage.resizableImage(withCapInsets: stretchInsets, resizingMode: .stretch)
        return bubbleImage
    }
    
    var bubbleCoverImage: UIImage {
        let stretchInsets = UIEdgeInsets(top: 30, left: 28, bottom: 23, right: 28)
        // 绘制 coverImage，盖住图片
        let stretchCoverImage = fromMe ? Asset.iconCoverSender.image : Asset.iconCoverReveiver.image
        let bubbleCoverImage = stretchCoverImage.resizableImage(withCapInsets: stretchInsets, resizingMode: .stretch)
        return bubbleCoverImage
    }
    
    func insert(model: SEChatRecord, more: Bool = false) {
        if !more {
            chatRecordList.append(model)
        } else {
            chatRecordList.insert(model, at: 0)
        }
    }
    
    func makeSureChatRecordIsUnique(model: SEChatRecord) -> Bool {
        return !chatRecordList.contains(where: { $0.msgId == model.msgId })
    }
    
    var checkReport: SEChatCheckReport?
    
    private convenience init(chatRecord: SEChatRecord) {
        self.init()
        self.chatRecord = chatRecord
    }
    
    func viewMode(at indexPath: IndexPath) -> SEChatViewModel {
        let viewModel = SEChatViewModel(chatRecord: chatRecordList[indexPath.row])
        viewModel.inquiryInfo = inquiryInfo
        
        if viewModel.messageType == .checkReport {
            let model = SEChatCheckReport.deserialize(from: viewModel.chatRecord.content)
            viewModel.checkReport = model
        }
        
        return viewModel
    }
    
    var fileAuthorizeStatus: SEPatientFileAuthorizedStatus {
        guard let content = chatRecord.content else { return .unauthorized }
        
        return SEPatientFileAuthorizedStatus(rawValue: content) ?? .unauthorized
    }
    
    var prescriptionPrice: String {
        String(format: "%.2f元", (chatRecord.prescription?.totalExpense ?? 0.0) / 100)
    }
    
    var cellHeight: CGFloat {
        switch messageType {
        case .text, .videoCall:
            var height = kChatAvatarMarginTop + kChatMargin
            let stringHeight = richTextLinePositionModifier.heightForLineCount(Int(richTextLayout?.rowCount ?? 0))

            height += max(stringHeight + kChatBubbleHeightBuffer + kChatBubbleBottomTransparentHeight, kChatBubbleImageViewHeight)
            
            return height
        case .image:
            var height = kChatAvatarMarginTop + kChatMargin
            height += kChatImageMaxHeight + 20 // 图片距离底部的距离 20
            return height
        case .start, .end, .time:
            return 40
        case .prescription:
            return chatRecord.prescription?.status == .noDrug ? 140 : 195
        case .checkReport:
            return 167
        case .fileAuthorize, .endInquiry:
            return 157
        default:
            return 0
        }
    }
    
    func changeMessageSendStatus(_ model: SEChatRecord) -> IndexPath? {
        guard let index = chatRecordList.firstIndex(where: { $0.msgId == model.msgId }) else { return nil }
        chatRecordList.remove(at: index)
        chatRecordList.insert(model, at: index)
        encodeRecordForChat()
        return IndexPath(row: index, section: 0)
    }
    
    func chatRecord(_ content: String?, url: String? = nil, messageType: SEChatMessageType) -> SEChatRecord {
        let model = SEChatRecord(fromUserId: UserDefaults.userId,
                                 toUserId: inquiryInfo.receiver,
                                 relationId: inquiryInfo.id,
                                 createTime: Date().toString(),
                                 msgId: messageId,
                                 relativeType: SEChatSenderType.patient.rawValue,
                                 msgType: messageType.rawValue,
                                 chatType: inquiryInfo.type.chatType.rawValue,
                                 content: content,
                                 url: url,
                                 ackType: inquiryInfo.type.chatType.ackType.rawValue,
                                 cmd: SEChatCMD.message.rawValue,
                                 messageSendStatus: .sending)
        return model
    }
    
    func updateFileAuthorizationMessage(indexPath: IndexPath) -> SEChatRecord {
        var tempModel = chatRecordList[indexPath.row]
        tempModel.content = inquiryInfo.authorizationStatus.rawValue
        chatRecordList[indexPath.row] = tempModel
        
        let model = SEChatRecord(fromUserId: tempModel.toUserId,
                                 toUserId: tempModel.fromUserId,
                                 relationId: tempModel.relationId,
                                 createTime: tempModel.createTime,
                                 msgId: tempModel.msgId,
                                 relativeType: SEChatSenderType.patient.rawValue,
                                 msgType: tempModel.msgType,
                                 chatType: inquiryInfo.type.chatType.rawValue,
                                 content: tempModel.content,
                                 ackType: inquiryInfo.type.chatType.ackType.rawValue,
                                 cmd: SEChatCMD.message.rawValue,
                                 messageSendStatus: .sending)
        return model
    }
    
    func updateInquiryDealStatus(_ status: SEInquiryChangeStatus) {
        inquiryInfo.dealStatus = status
        encodeDetailForChat()
    }
    
    // MARK: - 病情描述
    
    convenience init(inquiryInfo: SEInquiryInfo, withLoading: Bool = true) {
        self.init(withLoading: withLoading)
        self.inquiryInfo = inquiryInfo
    }
    
    var patientSex: String { inquiryInfo.patientSex == 1 ? "男" : "女" }
    var patientAge: String { "\(inquiryInfo.patientAge ?? "")岁" }
    
    var isTableHeaderExpand: Bool = false {
        didSet {
            let descMaxHeight = inquiryInfo.illness?.height(width: UIScreen.screenWidth - 15 * 2 - 14 * 2,
                                                            font: .regular(14)) ?? 0
            let descDoubleHeight = inquiryInfo.illness?.heightForSingleLine(font: .regular(14)) ?? 0 * 2
            
            var descHeight: CGFloat = 0
            
            if isTableHeaderExpand {
                collectionViewHeight = numberOfItems == 0 ? 0 : kChatInquiryInfoHeaderCollectionDefaultHeight
                let allergyHeight = inquiryInfo.allergicContent?.height(width: UIScreen.screenWidth - 15 * 2 - 14 * 2, font: .regular(14)) ?? 0
                let pastHeight = inquiryInfo.pastHistory?.height(width: UIScreen.screenWidth - 15 * 2 - 14 * 2, font: .regular(14)) ?? 0
                descHeight = max(descMaxHeight, descDoubleHeight) + allergyHeight + pastHeight + (15 + 6 + 20) * 2
            } else {
                collectionViewHeight = 0
                descHeight = min(descMaxHeight, descDoubleHeight)
            }
            tableHeaderViewHeight = kChatInquiryInfoHeaderDefaultHeight + descHeight + collectionViewHeight - kChatInquiryInfoHeaderDescLabelDetfaultHeight
        }
    }
    
    private(set) var tableHeaderViewHeight: CGFloat = kChatInquiryInfoHeaderDefaultHeight
    
    private(set) var collectionViewHeight: CGFloat = 0
    
    var numberOfItems: Int { inquiryInfo.picList?.count ?? 0 }
    
    // MARK: - 网络请求
    
    private var httpStatus: SEHttpStatus = .success
    
    /// 获取诊断数据
    func getDataOfInquiry() {
        page = 1
        let requestQueue = DispatchQueue(label: "loadDataOfGuideInquiry")
        let group = DispatchGroup()
        let semaphore = DispatchSemaphore(value: 1)
        
        group.enter()
        requestQueue.async {
            semaphore.wait()
            if self.inquiryInfo.type == .guide {
                SEHttpManager.shared.request(SEHospitalizeAPI.detailOfGuideInquiry(id: self.inquiryInfo.id ?? ""), model: SEInquiryInfo.self) { (model, httpStatus) in
                    self.httpStatus = httpStatus
                    switch httpStatus {
                    case .success:
                        self.inquiryInfo = model ?? self.inquiryInfo
                        self.inquiryInfo.type = .guide
                        semaphore.signal()
                    case .auth(let message), .failure(let message):
                        SEHUD.showError(message: message)
                        group.leave()
                    }
                }
            } else {
                SEHttpManager.shared.request(SEHospitalizeAPI.detailOfOnlineInquiry(id: self.inquiryInfo.id ?? ""), model: SEInquiryInfo.self) { (model, httpStatus) in
                    self.httpStatus = httpStatus
                    switch httpStatus {
                    case .success:
                        self.inquiryInfo = model ?? self.inquiryInfo
                        self.inquiryInfo.type = .online
                        
                        self.encodeDetailForChat()
                        
                        semaphore.signal()
                    case .auth(let message), .failure(let message):
                        SEHUD.showError(message: message)
                        group.leave()
                    }
                }
            }
            
            if !self.isFirstLoad {
                semaphore.wait()
                SEHttpManager.shared.request(SEChatAPI.listOfChatRecords(relationId: self.inquiryInfo.id ?? "", pageNum: self.page, pageSize: self.pageSize), model: SEChatModel.self) { (model, httpStatus) in
                    self.httpStatus = httpStatus
                    switch httpStatus {
                    case .success:
                        let list = self.handleChatRecordListData(model?.list)
                        self.chatRecordList = list
                        
                        self.handlePushMessageData()
                        
                        self.hasMore = model?.totalPage ?? 0 > self.page
                        
                        self.encodeRecordForChat()
                        
                    case .auth(let message), .failure(let message):
                        SEHUD.showError(message: message)
                    }
                    semaphore.signal()
                    group.leave()
                }
            } else {
                semaphore.wait()
                SEHttpManager.shared.request(SEChatAPI.listOfChatRecords(relationId: self.inquiryInfo.id ?? "", pageNum: self.page, pageSize: self.pageSize), model: SEChatModel.self) { (model, httpStatus) in
                    self.httpStatus = httpStatus
                    switch httpStatus {
                    case .success:
                        let list = self.handleChatRecordListData(model?.list)
                        self.chatRecordList = list
                        self.hasMore = model?.totalPage ?? 0 > self.page
                        semaphore.signal()
                    case .auth(let message), .failure(let message):
                        SEHUD.showError(message: message)
                        group.leave()
                    }
                }
                
                semaphore.wait()
                SEHttpManager.shared.requestForAny(SEChatAPI.sendGuideInquirySuccessMessage(id: self.inquiryInfo.id ?? "")) { (result, httpStatus) in
                    self.httpStatus = httpStatus
                    switch httpStatus {
                    case .success: break
                    case .auth(let message), .failure(let message):
                        SEHUD.showError(message: message, position: .center)
                    }
                    semaphore.signal()
                    group.leave()
                }
            }
        }
        
        group.notify(queue: DispatchQueue.main) {
            self.isCountDown = true
            self.updateData?(self.httpStatus)
        }
    }
    
    /// 获取更多聊天记录
    func getMoreListOfChatRecords() {
        page = page + 1
        SEHttpManager.shared.request(SEChatAPI.listOfChatRecords(relationId: inquiryInfo.id ?? "", pageNum: page, pageSize: pageSize), model: SEChatModel.self) { (model, httpStatus) in
            var list = [SEChatRecord]()
            switch httpStatus {
            case .success:
                list = self.handleChatRecordListData(model?.list)
                self.chatRecordList.insert(contentsOf: list, at: 1)
                self.hasMore = model?.totalPage ?? 0 > self.page
            case .auth(let message), .failure(let message):
                SEHUD.showError(message: message)
            }
            self.reloadTableView?(list, httpStatus)
        }
    }
    
    /// 生成消息id
    func generateMessageId(_ messageType: SEChatMessageType) {
        SEHttpManager.shared.requestForAny(SEChatAPI.generateMessageId(relationId: inquiryInfo.id ?? "", msgType: inquiryInfo.type.msgType)) { (result, httpStatus) in
            switch httpStatus {
            case .success:
                self.messageId = result as? String
            case .failure(let message):
                SEHUD.showError(message: message, position: .center)
            case .auth: break
            }
            self.generateMessageIdSuccess?(messageType, httpStatus)
        }
    }
    
    /// 修改问诊状态
    func changeOnlineInquiryStatus(_ status: SEInquiryChangeStatus, at indexPath: IndexPath? = nil) {
        guard let indexPath = indexPath else { return }
        
        SEHttpManager.shared.requestForAny(SEHospitalizeAPI.changeInquiryStatus(id: inquiryInfo.id ?? "", status: status.rawValue), withLoading: withLoading) { (result, httpStatus) in
            switch httpStatus {
            case .success:
                self.updateInquiryDealStatus(status)
                
                if indexPath.row < self.chatRecordList.count {
                    var chatRecord = self.chatRecordList[indexPath.row]
                    chatRecord.content = status.rawValue
                    self.chatRecordList[indexPath.row] = chatRecord
                    
                    self.encodeRecordForChat()
                }
                
            case .failure(let message):
                SEHUD.showError(message: message, position: .center)
            case .auth: break
            }
            self.confirmOnlineInquiryHandler?(httpStatus)
        }
    }
    
    /// 更新就诊档案授权状态
    func updateFileAuthorizationStatus(_ status: SEPatientFileAuthorizedStatus, at indexPath: IndexPath) {
        SEHttpManager.shared.requestForAny(SEChatAPI.updateFileAuthorizationStatus(id: inquiryInfo.id ?? "", status: status), withLoading: withLoading) { result, httpStatus in
            switch httpStatus {
            case .success:
                self.inquiryInfo.authorizationStatus = status
            case .failure(let message):
                SEHUD.showError(message: message, position: .center)
            case .auth: break
            }
            self.updateFileAuthorizationHandler?(indexPath, httpStatus)
        }
    }
}

extension SEChatViewModel {
    private func handleChatRecordListData(_ list: [SEChatRecord]?) -> [SEChatRecord] {
        guard let list = list, list.count > 0 else { return [] }
        
        var returnList = [SEChatRecord]()
        var temp: SEChatRecord?
        for var model in list.reversed() {
            if temp == nil || model.isLateForTwoMinutes(temp!) {
                guard let createTime = model.createTime else { continue }
                
                SELog(model.timeDate)
                
                returnList.insert(SEChatRecord(createTime: createTime,
                                               msgType: SEChatMessageType.time.rawValue,
                                               content: model.timeDate?.chatTimeString),
                                  at: returnList.count)
            }
            if model.msgType == SEChatMessageType.prescription.rawValue {
                model.prescription = SEChatRecord.Prescription.deserialize(from: model.content)
            }
            returnList.insert(model, at: returnList.count)
            temp = model
        }
        SELog(returnList)
        return returnList
    }
    
    private func handlePushMessageData() {
        switch inquiryInfo.dealStatus {
        case .confirming:
            insertStartEndCell()
        case .confirmEnd, .commented:
            insertStartEndCell(true)
        default:
            break
        }
    }
}

// MARK: - 聊天界面 插入 cell

extension SEChatViewModel {
    private func insertStartEndCell(_ end: Bool = false) {
        let model = SEChatRecord(msgType: SEChatMessageType.start.rawValue)
        insert(model: model, more: true)
        
        if end {
            let end = SEChatRecord(msgType: SEChatMessageType.end.rawValue)
            insert(model: end)
        }
    }
    
    func insertConfirmEndCell(_ status: SEInquiryChangeStatus) -> IndexPath {
        updateInquiryDealStatus(status)
        
        let model = SEChatRecord(msgType: SEChatMessageType.endInquiry.rawValue, content: status.rawValue)
        insert(model: model)
        
        encodeRecordForChat()
        
        let insertIndexPath = IndexPath(row: numberOfRows - 1, section: 0)
        return insertIndexPath
    }
    
    func insertChatRecordOfMessage(_ model: SEChatRecord) -> [IndexPath] {
        func insertMessageCell(_ model: SEChatRecord) -> IndexPath {
            insert(model: model)
            let insertIndexPath = IndexPath(row: numberOfRows - 1, section: 0)
            return insertIndexPath
        }
        
        guard let createTime = model.createTime else {
            let insertIndexPath = insertMessageCell(model)
            return [insertIndexPath]
        }
        
        let temp = chatRecordList.last
        if temp == nil || model.isLateForTwoMinutes(temp!) {
            var insertIndexPaths = [IndexPath]()
            chatRecordList.insert(SEChatRecord(createTime: createTime,
                                               msgType: SEChatMessageType.time.rawValue,
                                               content: model.timeDate?.chatTimeString),
                              at: chatRecordList.count)
            let timeIndexPath = IndexPath(row: numberOfRows - 1, section: 0)
            insertIndexPaths.append(timeIndexPath)
            let messageIndexPath = insertMessageCell(model)
            insertIndexPaths.append(messageIndexPath)
            encodeRecordForChat()
            return insertIndexPaths
        } else {
            let insertIndexPath = insertMessageCell(model)
            encodeRecordForChat()
            return [insertIndexPath]
        }
    }
}

// MARK: - 数据持久化

extension SEChatViewModel {
    func decode() {
        guard inquiryInfo.type == .online else { return }
        
        let requestQueue = DispatchQueue(label: "decodeDataForChat")
        let group = DispatchGroup()
        let semaphore = DispatchSemaphore(value: 1)
        
        group.enter()
        requestQueue.async {
            self.decodeDetailForChat(semaphore)
            self.decodeRecordForChat(semaphore)
            group.leave()
        }
        
        group.notify(queue: DispatchQueue.main) {
            self.isCountDown = false
            self.updateData?(.success)
        }
    }
    
    private func decodeDetailForChat(_ semaphore: DispatchSemaphore) {
        semaphore.wait()
        guard let inquiryId = inquiryInfo.id,
              let saveInfo = UserDefaults.standard.object(forKey: "chatPatientInfo-\(inquiryId)") as? Data else {
            semaphore.signal()
            return
        }
        
        let jsonDecoder = JSONDecoder()
        
        guard let patientInfo = try? jsonDecoder.decode(SEInquiryInfo.self, from: saveInfo) else {
            semaphore.signal()
            return
        }
        
        inquiryInfo = patientInfo
        
        semaphore.signal()
    }
    
    private func decodeRecordForChat(_ semaphore: DispatchSemaphore) {
        semaphore.wait()
        guard let inquiryId = inquiryInfo.id,
              let saveChatRecords = UserDefaults.standard.object(forKey: "chatRecordFirstPage-\(inquiryId)") as? Data else {
            semaphore.signal()
            return
        }
        
        let jsonDecoder = JSONDecoder()
        
        guard let chatRecords = try? jsonDecoder.decode([SEChatRecord].self, from: saveChatRecords) else {
            semaphore.signal()
            return
        }
        
        chatRecordList = chatRecords
        
        semaphore.signal()
    }
    
    private func encodeDetailForChat() {
        guard inquiryInfo.type == .online else { return }
        
        guard let inquiryId = inquiryInfo.id else { return }
        
        let jsonEncoder = JSONEncoder()
        
        guard let saveInfo = try? jsonEncoder.encode(inquiryInfo) else { return }
        
        UserDefaults.standard.set(saveInfo, forKey: "chatPatientInfo-\(inquiryId)")
    }
    
    private func encodeRecordForChat() {
        guard inquiryInfo.type == .online else { return }
        
        guard let inquiryId = inquiryInfo.id else { return }
        
        let jsonEncoder = JSONEncoder()
        
        guard let saveChatRecords = try? jsonEncoder.encode(chatRecordList) else { return }
        
        UserDefaults.standard.set(saveChatRecords, forKey: "chatRecordFirstPage-\(inquiryId)")
    }
}
